package org.springframework.web.servlet.mvc.method.annotation;
import static org.assertj.core.api.BDDAssertions.then;
import static org.springframework.http.MediaType.APPLICATION_JSON;
import static io.github.resilience4j.circuitbreaker.CircuitBreaker.decorateRunnable;
import static io.github.resilience4j.circuitbreaker.event.CircuitBreakerEvent.Type.ERROR;
import static io.github.resilience4j.circuitbreaker.event.CircuitBreakerEvent.Type.IGNORED_ERROR;
import static io.github.resilience4j.circuitbreaker.event.CircuitBreakerEvent.Type.NOT_PERMITTED;
import static io.github.resilience4j.circuitbreaker.event.CircuitBreakerEvent.Type.STATE_TRANSITION;
import static io.github.resilience4j.circuitbreaker.event.CircuitBreakerEvent.Type.SUCCESS;
import static io.github.resilience4j.circuitbreaker.monitoring.endpoint.CircuitBreakerEventEmitter.createSseEmitter;
import static java.util.stream.Collectors.toList;
import org.junit.Test;
import org.springframework.http.MediaType;
import io.github.resilience4j.circuitbreaker.CircuitBreaker;
import io.github.resilience4j.circuitbreaker.CircuitBreakerConfig;
import io.github.resilience4j.circuitbreaker.CircuitBreakerRegistry;
import io.github.resilience4j.circuitbreaker.event.CircuitBreakerEvent;
import io.github.resilience4j.circuitbreaker.monitoring.endpoint.CircuitBreakerEventDTO;
import io.vavr.control.Try;
import java.io.IOException;
import java.time.Duration;
import java.util.ArrayList;
import java.util.ConcurrentModificationException;
import java.util.List;
/**
* @author bstorozhuk
*/
public class CircuitBreakerEventEmitterTest {
@Test
public void testEmitter() throws IOException {
CircuitBreakerConfig config = CircuitBreakerConfig.custom()
.ringBufferSizeInClosedState(3)
.ringBufferSizeInHalfOpenState(2)
.failureRateThreshold(66)
.waitDurationInOpenState(Duration.ofSeconds(1))
.recordFailure(e -> !(e instanceof IllegalArgumentException))
.build();
CircuitBreaker circuitBreaker = CircuitBreakerRegistry.ofDefaults().circuitBreaker("test", config);
Runnable run = decorateRunnable(circuitBreaker, () -> System.out.println("."));
Runnable fail = decorateRunnable(circuitBreaker, () -> {
throw new ConcurrentModificationException();
});
Runnable ignore = decorateRunnable(circuitBreaker, () -> {
throw new IllegalArgumentException();
});
SseEmitter sseEmitter = createSseEmitter(circuitBreaker.getEventStream());
TestHandler handler = new TestHandler();
sseEmitter.initialize(handler);
exec(run, 2);
exec(ignore, 1);
exec(fail, 3);
sseEmitter.complete();
assert handler.isCompleted;
exec(run, 2);
List<CircuitBreakerEvent.Type> events = handler.events.stream()
.map(CircuitBreakerEventDTO::getType)
.collect(toList());
then(events).containsExactly(SUCCESS, SUCCESS, IGNORED_ERROR, ERROR, ERROR, STATE_TRANSITION, NOT_PERMITTED);
}
private void exec(Runnable runnable, int times) {
for (int i = 0; i < times; i++) {
Try.runRunnable(runnable)
.onFailure(e -> System.out.println(e.getMessage()));
}
}
private static class TestHandler implements ResponseBodyEmitter.Handler {
public List<CircuitBreakerEventDTO> events = new ArrayList<>();
public boolean isCompleted = false;
private Runnable callback;
@Override public void send(Object data, MediaType mediaType) throws IOException {
if (APPLICATION_JSON == mediaType && data instanceof CircuitBreakerEventDTO) {
events.add(((CircuitBreakerEventDTO) data));
}
}
@Override public void complete() {
isCompleted = true;
callback.run();
}
@Override
public void completeWithError(Throwable failure) {
System.out.println("E");
}
@Override
public void onTimeout(Runnable callback) {
System.out.println("T");
}
@Override
public void onCompletion(Runnable callback) {
this.callback = callback;
}
}
}